<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Outta That Mess</title>
<script
	src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA3qyl_FEhGYSK9pSjzs-SVBRRmxvsDchdZPynv1Lw2iDuExpu_hRedE5TdRiy6czrOuJj2d3qm2FCLA"
	type="text/javascript"></script>
<script
	src="http://gmaps-utility-library.googlecode.com/svn/trunk/extmaptypecontrol/release/src/extmaptypecontrol_packed.js"
	type="text/javascript"></script>

<link rel="stylesheet" type="text/css" href="outta-that-mess.css" />
</head>
<body onunload="GUnload()">

<div id="map"></div>
<div id="login">
<h4>Outta That Mess</h4>
<form method="POST" action="javascript:login()">
<table>
	<tr>
		<td>Username:</td>
		<td><input type="text" name="username" id="username" /></td>
	</tr>
	<tr>
		<td>Password:</td>
		<td><input type="password" name="password" id="password" /></td>
	</tr>
	<tr>
		<td colspan="2"><input type="submit" name="login" value="Submit" /></td>
	</tr>
	</td>
	</tr>
</table>
</form>
</div>
<div id="textArea">

<form action="javascript:getDirections()">
<table>
	<tr>
		<td>To:</td>
		<td><input type="text" name="daddr" id="daddr" /></td>
	</tr>
	<tr>
		<td>From:</td>
		<td><input type="text" name="saddr" id="saddr" /></td>
	</tr>
	<tr>
		<td>Walk <input type="checkbox" name="walk" id="walk" /></td>
		<td>Avoid Highways <input type="checkbox" name="highways"
			id="highways" /></td>
	</tr>
	<tr>
		<td></td>
		<td align="right"><input type="submit" name="submit" value="Get" /></td>
		</td>
		</tr>
		</table>
		</form>
<noscript>
<b>JavaScript must be enabled in order for you to use Google Maps.</b>
However, it seems JavaScript is either disabled or not supported by your
browser. To view Google Maps, enable JavaScript by changing your browser
options, and then try again.
</noscript>

<div id="alternate"></div>
<div id="directions"></div>
</div>

<script type="text/javascript">
    //<![CDATA[

    /**
     * TODO (perhaps) - undo implementation when adjusting an alternate
     */

    if (GBrowserIsCompatible()) {

      /**
       * What marker was dragged?
       */
      var markerDragged;

      /**
       * Has a marker been dragged?
       */
      var isDragged;

      /**
       * What is the index before the dragged index?
       */
      var prevIndex;

      /**
       * The existing markers.
       */
      var gmarkers = [];

      /**
       * The normal projection for the map
       */
      var normalProj = G_NORMAL_MAP.getProjection();

      /**
       * Directions object for text
       */
      var dirn0 = new GDirections();

      /**
       * Directions object for intial click
       */
      var dirn1 = new GDirections();

      /**
       * Directions object for adjusting the route
       */
      var dirn2 = new GDirections();

      /**
       * Existing polylines used to represent directions
       */
      var gpolys = [];

      /**
       * The original polyline for directions
       */
      var originalPolyline;

      /**
       * The nodes in the route
       */
      var routeNodes = [];

      /**
       * The last index
       */
      var lastindex = 0;

      /**
       * The original directions object
       */
      var originalDirections;

      /**
       * Show the original polyline? Defaults to true.
       */
      var shouldShowOriginal = true;

      /**
       * Save a route from dirn2?
       */
      var shouldSave = false;

      /**
       * The node for the icon for dragging
       */
      var iconNode = new GIcon();
      iconNode.image = 'node.gif';
	  iconNode.shadow = '';
	  iconNode.iconSize = new GSize(10,10);
	  iconNode.shadowSize = new GSize(0,0);
	  iconNode.iconAnchor = new GPoint(5,5);
	  iconNode.infoWindowAnchor = new GPoint(5,5);
	  iconNode.dragCrossImage = 'empty.gif'; // undocumented String: indicates an image to be used as the drag cross. If you set it to the null string, you get the default drag_cross_67_16.png image.
	  iconNode.dragCrossSize = GSize(1, 1); //undocumented GSize(): indicates the size of the drag cross.
	  iconNode.maxHeight = 1; //undocumented integer: The maximum difference in height between the marker anchor and the drag cross anchor during dragging. Setting the maxHeight to zero causes it to use the default 13.

	  /**
	   * Flag a marker to remove
	   */
	  var toRemoveIndex = null;

	  /**
	   * The map
	   */
      var map = new GMap2(document.getElementById("map"));
      map.addControl(new GLargeMapControl());
      map.addControl(new ExtMapTypeControl({showTraffic: true, showTrafficKey: true}));
      map.setCenter(new GLatLng(38.90636,-77.2080421), 9);
      map.enableContinuousZoom();

      /**
       * A div containing the context menu
       */
      var contextmenu = document.createElement("div");
      contextmenu.style.visibility="hidden";
      contextmenu.style.background="#ffffff";
      contextmenu.style.border="1px solid #8888FF";
      contextmenu.innerHTML = '<a href="javascript:removeMarker()"><div>&nbsp;&nbsp;Remove Destination&nbsp;&nbsp;</div></a>';
	  map.getContainer().appendChild(contextmenu);

      /**
       * The node used for dragging the line
       */
      var myNode;
      myNode = new GMarker(map.getCenter(), {icon: iconNode, draggable:true, bouncy:false,
       	zIndexProcess:function(marker,b) {return 1;}});
	  map.addOverlay(myNode);
	  myNode.show(); // sometimes .hide() does not work without .show() at first ???
	  myNode.hide(); // hide this marker initially

      /**
       * Given a directions object update the directions text area with a nicely formatted
       * version with correct links.
       * @param directions a GDirections object
       */
      function showInitialTextDirections(directions) {
			var stepHtml = "<table><tr><td>";
			if(directions.getNumRoutes() > 0) {
				var route = directions.getRoute(0);
				stepHtml += route.getStartGeocode().address + "<br /></td></tr>";
			}
			for(var i = 0; i < directions.getNumRoutes(); i++) {
				for(var n = 0; n < route.getNumSteps(); n++) {
					stepHtml += "<tr><td>" + route.getStep(n).getDescriptionHtml() + "<br /></td></tr>";
				}
			}
			if(directions.getNumRoutes() > 0) {
			  var route = directions.getRoute(0);
			  stepHtml += "<tr><td>" + route.getEndGeocode().address + "</td></tr>";
		    }
			stepHtml += "<tr><td>" + directions.getDistance().html +
			    " (about " + directions.getDuration().html + ")<br /></td></tr></table>";
			var existingHtml = document.getElementById("directions").innerHTML;
			document.getElementById("directions").innerHTML = stepHtml;
			originalDirections = directions;
      }

      /**
       * Given the new route, updates the existing route information with it.
       */
      function updateTextDirections(directions) {
			var stepHtml = "<table><tr><td>";
			if(originalDirections.getNumRoutes() > 0) {
				var route = originalDirections.getRoute(0);
				stepHtml += route.getStartGeocode().address + "<br /></td></tr>";
			}
			for(var i = 0; i < directions.getNumRoutes(); i++) {
			    var route = directions.getRoute(i);
				for(var n = 0; n < route.getNumSteps(); n++) {
					stepHtml += "<tr><td>" + route.getStep(n).getDescriptionHtml() + "<br /></td></tr>";
				}
			}
			if(originalDirections.getNumRoutes() > 0) {
			  var route = originalDirections.getRoute(originalDirections.getNumRoutes() - 1);
			  stepHtml += "<tr><td>" + route.getEndGeocode().address + "</td></tr>";
		    }
			stepHtml += "<tr><td>" + directions.getDistance().html +
			    " about " + directions.getDuration().html + "<br /></td></tr></table>";
			document.getElementById("alternate").innerHTML = stepHtml;
      }

      /**
       * Clear everything off to start over.
       */
      function clearMap() {
        if(originalPolyline != null) {
          map.removeOverlay(originalPolyline);
        }
        for(var i = 0; i < gmarkers.length; i++) {
          map.removeOverlay(gmarkers[i]);
        }
        for(var i = 0; i < gpolys.length; i++) {
          map.removeOverlay(gpolys[i]);
        }
        originalPolyline = null;
        shouldShowOriginal = true;
        gmarkers = [];
        gpolys = [];
      }

      /**
       * Given a directions object, update the map with the new initial directions
       * @param directions a GDirections object
       */
      function showInitialMapRouteFromClicks(directions) {
      	var gp = directions.getPolyline();
		var route = directions.getRoute(0);
		var point = gp.getVertex(gp.getVertexCount() - 1); // snap to last vertex in the polyline
		var endMarker = placeEndpointMarker(point, route);

		if (gmarkers.length > 1) {
		    showInitialTextDirections(directions);
            showStartPolyline(gp);

            getAlternateInfoWindow(endMarker,
              route.getStep(0).getLatLng().toUrlValue(6),
              route.getEndLatLng().toUrlValue(6), true);
		}
       }

       /**
        * Given the final directions, get the information on possible alternate routes
        */
       function getAlternateInfoWindow(marker, saddr, daddr, display) {
         var xmlHttp = GXmlHttp.create();
         xmlHttp.onreadystatechange=function() {
           if(xmlHttp.readyState==4) {
             if(marker.infohtml == null || marker.infohtml == undefined) {
               marker.infohtml = xmlHttp.responseText;
             }
             else {
               marker.infohtml = marker.infohtml + xmlHttp.responseText;
             }
             if(display) {
	           marker.openInfoWindowHtml(marker.infohtml);
	         }
           }
         }
         xmlHttp.open("GET", "/outta-that-mess/resources/alternates/"
           +escape(saddr)+"/"+escape(daddr), true);
         xmlHttp.send(null);
       }

       /**
        * Given the final directions, get the information on possible alternate routes
        */
       function putAlternateRoute(waypoints) {
         if(waypoints.length > 0) {
           var xmlHttp = GXmlHttp.create();
           var addresses = waypoints[0] + ";";
           for(var n = 1; n < waypoints.length; n++) {
             addresses = addresses + waypoints[n] + ";";
           }

           xmlHttp.open("PUT", "/outta-that-mess/resources/alternates/store", true);
           xmlHttp.send(addresses);
         }
       }

       /**
        * Publish a route.
        */
       function publish() {
         var name = document.getElementById("routeName").value;
         var addresses = dirn2.getRoute(0).getStep(0).getLatLng().toUrlValue(6) + ";";
		 for(var n = 0; n < dirn2.getNumRoutes(); n++) {
			  addresses = addresses + dirn2.getRoute(n).getEndLatLng().toUrlValue(6) + ";";
		  }

         var xmlHttp = GXmlHttp.create();
         xmlHttp.onreadystatechange=function() {
           if(xmlHttp.readyState==4) {
             if(xmlHttp.status==401) {
               document.getElementById("login").style.border = "medium solid #BBBB66";
             }
             else {
               document.getElementById("sandbox").innerHTML = "";
             }
           }
         }
         xmlHttp.open("PUT", "/outta-that-mess/resources/alternates/publish/" + escape(name), true);
         xmlHttp.send(addresses);
       }

       /**
        * Given a start address, destination address, and alternate route number
        * display the alternate.
        */
       function displayAlternate(number, slat, slong, dlat, dlong) {
         GDownloadUrl("http://localhost:8080/outta-that-mess/resources/alternates/published/" +
             escape(slat) + "%2C" + escape(slong) + "/" + escape(dlat) + "%2C" +
             escape(dlong) + "/" + escape(number),
             function(text, status) {
               if(status >= 200 && status < 300) {
                 shouldSave = false;
                 lastIndex = 0;
                 dirn2.load(text, {getPolyline: true, getSteps: true});
               }
               else {
                 alert("Alternate not retrieved: " + status);
               }
             });
       }


       /**
        * Given a start address, destination address, and creator computer ID
        * display the alternate.
        */
       function displaySandbox(computerId, slat, slong, dlat, dlong) {
         GDownloadUrl("http://localhost:8080/outta-that-mess/resources/alternates/sandbox/" +
             escape(slat) + "%2C" + escape(slong) + "/" + escape(dlat) + "%2C" +
             escape(dlong) + "/" + escape(computerId),
             function(text, status) {
               if(status >= 200 && status < 300) {
                 shouldSave = false;
                 lastIndex = 0;
                 dirn2.load(text, {getPolyline: true, getSteps: true});
               }
               else {
                 alert("Alternate not retrieved: " + status);
               }
             });
       }

      /**
       * Given a directions object that is intended to encompass the whole route all the time,
       * load it.
       * @param directions the directions to use
       */
      function showInitialMapRouteFromText(directions) {
        clearMap();

        var gp = directions.getPolyline();
        var route = directions.getRoute(0);
      	var start = gp.getVertex(0);
      	placeEndpointMarker(start, route);

        var end = gp.getVertex(gp.getVertexCount() - 1);
        var endMarker = placeEndpointMarker(end, route);

		showInitialTextDirections(directions);
        showStartPolyline(gp);
        getAlternateInfoWindow(endMarker,
              route.getStep(0).getLatLng().toUrlValue(6),
              route.getEndLatLng().toUrlValue(6), true);
      }

       /**
        * Create a marker and put it on the map
        * @param point the point at which to place the marker
        * @param route the route at which this point is an endpoint
        */
       function placeEndpointMarker(point, route) {
           var marker = new GMarker(point, {draggable:false, dragCrossMove:false, bouncy:false, zIndexProcess:function(marker,b) {return 1;}});

		   if (gmarkers.length == 0) {
			  marker.title = route.getStartGeocode().address;
		   } else {
			  gmarkers[gmarkers.length-1].title = route.getStartGeocode().address;
			  marker.title = route.getEndGeocode().address;
		   }
		   marker.MyIndex = gmarkers.length;
		   gmarkers.push(marker);
		   map.addOverlay(marker);
		   return marker;
       }

      /**
       * Show a polyline on the map.
       */
       function showStartPolyline(polyline) {
            if(originalPolyline == null) {
               originalPolyline = polyline.copy();
               originalPolyline.setStrokeStyle({color: "#FF0000", opacity: 0.333});
            }
            map.addOverlay(polyline);
            gpolys.push(polyline);
		    routeNodes = [];
		    getProximity();
       }

       /**
        * Remove the original polyline from the map
        */
       function hideOriginalPolyline() {
         if(originalPolyline != null && originalPolyline != undefined) {
           map.removeOverlay(originalPolyline);
         }
         shouldShowOriginal = false;
       }

      /**
       * Get directions from the text form
       */
      function getDirections() {
        // ==== Set up the walk and avoid highways options ====
        var opts = {};
        if (document.getElementById("walk").checked) {
           opts.travelMode = G_TRAVEL_MODE_WALKING;
        }
        if (document.getElementById("highways").checked) {
           opts.avoidHighways = true;
        }

        opts.getPolyline = true;
        opts.getSteps = true;

        // ==== set the start and end locations ====
        var saddr = document.getElementById("saddr").value
        var daddr = document.getElementById("daddr").value

 		geocoder = new GClientGeocoder();
 		geocoder.getLatLng(saddr, function(point){saddr = point;} )
 		geocoder.getLatLng(daddr, function(point){daddr = point;} )

        dirn0.load("from: " + saddr + " to: " + daddr, opts);
      }

      function login() {
         var username = document.getElementById("username").value;
         var xmlHttp = GXmlHttp.create();
         xmlHttp.open("POST", "/outta-that-mess/resources/alternates/" + escape(username));
         xmlHttp.send(null);
      }

      /**
       * Handle a drag on the marker
       */
      function dragMarker() {
        shouldSave = true;
        if (isDragged == 2) { // exit if already waiting for dirn2.load to complete
		  markerDragged = this;
		  return;
	    }
	    isDragged = 2; // tag that calculation of new route is started

	    if (markerDragged) { //determine which marker triggered the recalculation
		  marker = markerDragged;
		  markerDragged = false;
	    }
	    else {
		  marker = this;
	    }

	    lastIndex = marker.MyIndex;
	    var point = marker.getPoint();

	    if (lastIndex > 0) {
		  if (lastIndex < gmarkers.length - 1) {
			prevIndex = lastIndex - 1;
		  }
		  else {
			prevIndex = -1;
			lastIndex = lastIndex - 1;
       	  }
       	  var waypoints = [];
       	  for(var n = 0; n < gmarkers.length; n++) {
       	      waypoints.push(gmarkers[n].getPoint());
       	  }
		  // recalculate route
		  dirn2.loadFromWaypoints(waypoints, {getPolyline:true, getSteps:true});
	    }
    }

    /**
     * Remove a midpoint marker.  Assumes it is called from a context menu with
     * toRemoveIndex set.
     */
    function removeMarker() {
      contextmenu.style.visibility = "hidden";
      if(toRemoveIndex) {
        var marker = gmarkers.splice(toRemoveIndex, 1);
        map.removeOverlay(marker);
       	var waypoints = [];
       	for(var n = 0; n < gmarkers.length; n++) {
       	      waypoints.push(gmarkers[n].getPoint());
       	}
		// recalculate route
		dirn2.loadFromWaypoints(waypoints, {getPolyline:true, getSteps:true});
      }
    }

    /**
     * Called to insert alternate addresses via text
     */
    function insertAlternate(addresses) {
    }

    /**
     * Get the proximity of the mouse to the current route line.
     */
	function getProximity(mouseLatLng, marker) {
		var dist;
		var zoom;

		if (routeNodes.length == 0) {
			dist = 0;
			zoom = map.getZoom();

			if (gpolys.length > 0 && gpolys[0].getVertexCount() > 0 ) {
				routeNodes.push(normalProj.fromLatLngToPixel(gpolys[0].getVertex(0), zoom));
			}
			for (var i = 0; i < gpolys.length; i++) {
				dist += gpolys[i].getLength();

				for (var j = 1; j < gpolys[i].getVertexCount(); j++) {
					var point = normalProj.fromLatLngToPixel(gpolys[i].getVertex(j), zoom)
					point.MyIndex = i; // store the index of polyline containing this node
					routeNodes.push(point);
				}
			}
		}

		if (!mouseLatLng || routeNodes.length <= 1 || isDragged > 0) {
			return;
		}

		zoom = map.getZoom();
		var mousePx = normalProj.fromLatLngToPixel(mouseLatLng, zoom);

		var minDist = 999;
		var minX = mousePx.x;
		var minY = mousePx.y;

		if (routeNodes.length > 1) {
			var x,y, d1,d2,d;
			var dx = mousePx.x - routeNodes[0].x;
			var dy = mousePx.y - routeNodes[0].y;
			d2 = dx*dx + dy*dy; // distance^2 from mouse to start of segment 1 in pixels

			for (var n = 0 ; ++n < routeNodes.length; ) {
				d1 = d2; // distance^2 from mouse to start of segment n in pixels

				x = routeNodes[n].x; dx = mousePx.x - x;
				y = routeNodes[n].y; dy = mousePx.y - y;
				d2 = dx*dx + dy*dy; // distance^2 from mouse to end of segment n in pixels

				dx = x - routeNodes[n-1].x;
				dy = y - routeNodes[n-1].y;
				d = dx*dx + dy*dy; // lenght^2 of segment n

				var u = ((mousePx.x - x) * dx + (mousePx.y - y) * dy) / d; // a bit of vector algebra :)
				x += (u*dx); // x,y coordinates in pixels of closest point to mouse in segment n
				y += (u*dy);

				dx = mousePx.x - x;
				dy = mousePx.y - y;
				dist = dx*dx + dy*dy; // distance^2 from mouse to closest point in segment n

				if ((d1 - dist) + (d2 - dist) > d) {
					if (d1 < d2) {
						dist = d1;
						x = routeNodes[n-1].x;
						y = routeNodes[n-1].y;
					}
					else {
						dist = d2;
						x = routeNodes[n].x;
						y = routeNodes[n].y;
					}
				}

				if (minDist > dist) { // closest point in segment n is closest point overall so far
					minDist = dist;
					minX = x;
					minY = y;
					myNode.MyIndex = routeNodes[n].MyIndex; // remember segment closest to mouse
				}
			}

			if (minDist > 25) { // mouse is not close enough to the displayed line
				myNode.hide(); // do not display marker for dragging the polyline
			}
			else {
				for (n = gmarkers.length; --n >= 0;) { // check if mouse is not too close to existing gmarkers markers
					var markerPx = normalProj.fromLatLngToPixel(gmarkers[n].getPoint(), zoom);

					dx = markerPx.x - minX;
					dy = markerPx.y - minY;

					if (dx*dx + dy*dy < 25) { // mouse is too close to existing marker
						myNode.hide(); // do not show additional marker for dragging the line - the user is about to drag existing waypoint
						return;
					}
				}

				myNode.setPoint(normalProj.fromPixelToLatLng(new GPoint(minX, minY), zoom));
				myNode.show(); // display marker for dragging the polyline
			}
		}
	}

	function createPublishInfo(start, end) {
	  var html = '<form action="javascript:publish()">' +
	    'Name: <input type="text" id="routeName" /><br />' +
	    '<input type="submit" id="publish" value="Publish" /></form><br />';
	  for(var n = 0; n < gmarkers.length; n++) {
	    var marker = gmarkers[n];
	    marker.infohtml = html;
        getAlternateInfoWindow(marker, start, end, false);
	  }
	}

    /**
     * The user has dragged the node used for tuning the route.
     */
    GEvent.addListener(myNode, "drag", function() {
        shouldSave = true;
		myNode.show();

		if (isDragged == 2) { // already waiting for dirn2.load to complete - so just remember that marker was dragged again
			markerDragged = myNode; // remember which marker was dragged
			return;
		}

		if (myNode.MyIndex < gmarkers.length) {
			isDragged = 2; // tag that dirn2.load is started
			markerDragged = false;

			lastIndex = myNode.MyIndex;
			prevIndex = -1;

       	    var waypoints = []
       	    for(var n = 0; n <= lastIndex; n++) {
       	      waypoints.push(gmarkers[n].getPoint());
       	    }
       	    waypoints.push(myNode.getPoint().toUrlValue(6));
       	    for(var n = lastIndex + 1; n < gmarkers.length; n++) {
       	      waypoints.push(gmarkers[n].getPoint());
       	    }
			// recalculate route between gmarkers before and after myNode on the displayed line
			dirn2.loadFromWaypoints(waypoints, {getPolyline:true, getSteps:true});
		}
	});

    /**
     * A click occured on the map. Show an info window (if on an overlay) or add a point.
     */
	GEvent.addListener(map, "click", function(overlay, point) {
        contextmenu.style.visibility = "hidden";
	    if(overlay && overlay.infohtml) {
	      overlay.openInfoWindowHtml(overlay.infohtml);
	    } else if (point) {
			if (gmarkers.length == 0) { // no gmarkers exist yet - map was clicked for start of the route
				dirn1.loadFromWaypoints([point.toUrlValue(6), point.toUrlValue(6)], {getPolyline:true}); // get directions from that point to itself to snap it to street
			}
			else if(gmarkers.length == 1) { // map was clicked for additional waypoint
				dirn1.loadFromWaypoints([gmarkers[gmarkers.length-1].getPoint(), point.toUrlValue(6)], {getPolyline:true, getSteps:true}); //get directions from last waypoint to clicked point
			}
		}
	});

	/**
	 * Show the marker context menu
	 */
	GEvent.addListener(map, "singlerightclick", function(point,src,overlay) {
	    if(overlay) {
	      if(overlay instanceof GMarker &&
	         overlay != gmarkers[0] &&
	         overlay != gmarkers[gmarkers.length - 1] &&
	         overlay != myNode) {
          var x=point.x;
          var y=point.y;
          if (x > map.getSize().width - 120) { x = map.getSize().width - 120 }
          if (y > map.getSize().height - 100) { y = map.getSize().height - 100 }
          var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(x,y));
          pos.apply(contextmenu);
          contextmenu.style.visibility = "visible";
          toRemoveIndex = gmarkers.indexOf(overlay);
          }
        }
      });


    GEvent.addListener(dirn0,"load", function() {
        showInitialMapRouteFromText(dirn0);
    });

    GEvent.addListener(dirn1,"load", function() {
        showInitialMapRouteFromClicks(dirn1);
    });

    GEvent.addListener(dirn2,"load", function() {
        try {
    	var gp = dirn2.getPolyline();
	    map.removeOverlay(gpolys[gpolys.length - 1]);
		var minI;

		if (prevIndex >= 0) {
		 	map.removeOverlay(gpolys[gpolys.length - 2]);

			var minD;
			var points = [];
			var p0 = gmarkers[lastIndex].getPoint();

			for (var i = 0; i < gp.getVertexCount(); i++) { // search closest vertex to dragged waypoint for splitting received route at it into two routes between waypoints
				var p = gp.getVertex(i);
				points.push(p);

				var d = p0.distanceFrom(p);

				if (i == 0 || minD > d) {
					minD = d;
					minI = i;
				}
			}

			gpolys[gpolys.length - 2] = new GPolyline(points.slice(0, minI + 1)); //+1,  because slice extracts up to, but not including, the 'end' element
			gpolys[gpolys.length - 1] = new GPolyline(points.slice(minI, points.length));
			map.addOverlay(gpolys[length - 2]);
		}
		else { // last waypoint was dragged
			gpolys[gpolys.length - 1] = gp;
		}

        if(shouldShowOriginal) {
          map.addOverlay(originalPolyline);
        }
		map.addOverlay(gpolys[gpolys.length - 1]);

		routeNodes = [];
		getProximity();

		isDragged = 0; // tag that there is no dragged gmarkers or waiting for dirn2 to complete

		if (markerDragged) { // marker was dragged again until dirn2 was loaded
			isDragged = 1; // tag that there is dragged waypoint
			GEvent.trigger(markerDragged, 'drag'); // trigger recalculation of route
		} else { // only if the drag isn't still happening should we try this
		  updateTextDirections(dirn2);

		  if(shouldSave) {
		    var waypoints = [];
		    waypoints.push(dirn2.getRoute(0).getStep(0).getLatLng().toUrlValue(6));
		    for(var n = 0; n < dirn2.getNumRoutes(); n++) {
			  waypoints.push(dirn2.getRoute(n).getEndLatLng().toUrlValue(6));
		    }
		    putAlternateRoute(waypoints);

		    createPublishInfo(dirn2.getRoute(0).getStep(0).getLatLng().toUrlValue(6),
		      dirn2.getRoute(dirn2.getNumRoutes() - 1).getEndLatLng().toUrlValue(6));
		  }

		  var oldMarkers = gmarkers.splice(1, gmarkers.length - 2);
		  for(var n = 0; n < oldMarkers.length; n++) {
		    map.removeOverlay(oldMarkers[n]);
		  }

		  for(var n = 0; n < dirn2.getNumRoutes() - 1; n++) {
		   var route = dirn2.getRoute(n);
		   var addedNode = new GMarker(route.getEndLatLng(),
		       {icon: iconNode, draggable:true, bouncy:false,
       	       zIndexProcess:function(marker,b) {return 1;}});
       	   addedNode.MyIndex = n + 1;
       	   gmarkers.splice(addedNode.MyIndex, 0, addedNode);
       	   for(var n = 0; n < gmarkers.length; n++) {
       	     if(gmarkers[n].MyIndex >= addedNode.MyIndex) {
       	       gmarkers[n].MyIndex++;
       	     }
       	   }
       	   GEvent.addListener(addedNode, "drag", dragMarker);
       	   map.addOverlay(addedNode);
       	   }
		}
       	   } catch(e) {
       	     alert("ERROR: " + e.name + " " + e.message);
       	   }
      });

      // for detecting if mouse is above displayed route
      GEvent.addListener(map, 'mousemove', getProximity);

	  GEvent.addListener(map, "zoomend", function() {
	    routeNodes = [];
	  });

      document.getElementById("map").style.top="0%";
      document.getElementById("map").style.left="0%";
      document.getElementById("map").style.width="80%";
      document.getElementById("map").style.margin="0px";
      document.getElementById("map").style.padding="0px";

      if (window.attachEvent) {
        window.attachEvent("onresize", function(){  //IE
          var hght=document.documentElement.clientHeight;
          document.getElementById("map").style.height=hght+"px";
        });
      } else {
        document.getElementById("map").style.height="100%";
        window.addEventListener("resize", function() {}, false);
      }

    } else {
      alert("Sorry, the Google Maps API is not compatible with this browser");
    }
    //]]>
    </script>
</body>

</html>